#include "Graphics/PiccoloVKGraphicsContext.h"

#include "Window/PiccoloGLFWWindow.h"

namespace Piccolo
{
    // 必要的层
    const DeviceFeature requestedLayers[] =
    {
        {"VK_LAYER_KHRONOS_validation", true},
    };

    // 必要的扩展
    const DeviceFeature requestedExtensions[] =
    {
        {VK_KHR_SURFACE_EXTENSION_NAME, true},
#ifdef VK_USE_PLATFORM_WIN32_KHR
        { VK_KHR_WIN32_SURFACE_EXTENSION_NAME, true },
        {"VK_EXT_debug_report", true},
#elif PICCOLO_ENGINE_PLATFORM_MACOS
        { VK_MVK_MACOS_SURFACE_EXTENSION_NAME, true },
#elif PICCOLO_ENGINE_PLATFORM_LINUX
        { VK_KHR_XCB_SURFACE_EXTENSION_NAME, true },
#endif
    };

    PiccoloVKGraphicsContext::PiccoloVKGraphicsContext(PiccoloWindow *window)
    {
        CreateInstance();
        CreateSurface(window);
        findPhysicalDevice();
    }

    PiccoloVKGraphicsContext::~PiccoloVKGraphicsContext()
    {
        vkDestroySurfaceKHR(mInstance, mSurface, nullptr);
        vkDestroyInstance(mInstance, nullptr);
    }

    static VkBool32 VkDebugReportCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objectType, uint64_t object, size_t location, int32_t messageCode, const char* pLayerPrefix, const char* pMessage, void* pUserData)
    {
        if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT)
        {
            LOG_E("{0}", pMessage);
        }
        if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT || flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)
        {
            LOG_W("{0}", pMessage);
        }
        return VK_TRUE;
    }

    void PiccoloVKGraphicsContext::CreateInstance()
    {
        // 获取扩展
        uint32_t availableLayerCount;
        CALL_VK(vkEnumerateInstanceLayerProperties(&availableLayerCount, nullptr));
        VkLayerProperties availableLayers[availableLayerCount];
        CALL_VK(vkEnumerateInstanceLayerProperties(&availableLayerCount, availableLayers));

        uint32_t enableLayerCount;
        const char* enableLayers[32];
        if (bShouldValidate)
        {
            if (!checkDeviceFeatures("Instance Layers", false, availableLayerCount, availableLayers, ARRAYSIZE(requestedLayers), requestedLayers, &enableLayerCount, enableLayers))
            {
                return;
            }
        }

        // 构建extension
        uint32_t availableExtensionCount;
        CALL_VK(vkEnumerateInstanceExtensionProperties("", &availableExtensionCount, nullptr));
        VkExtensionProperties availableExtensions[availableExtensionCount];
        CALL_VK(vkEnumerateInstanceExtensionProperties("", &availableExtensionCount, availableExtensions));

        // GLFW自己需要的一些扩展，将GLFW和Vulkan的扩展统一加到容器中并加载
        uint32_t glfwRequestedExtensionCount;
        const char** glfwRequestedExtensions = glfwGetRequiredInstanceExtensions(&glfwRequestedExtensionCount);
        std::unordered_set<const char*> allRequestedExtensionSet;
        std::vector<DeviceFeature> allRequestedExtensions;
        // Vulkan的扩展
        for (const auto& extension : requestedExtensions)
        {
            if (allRequestedExtensionSet.find(extension.name) == allRequestedExtensionSet.end())
            {
                allRequestedExtensionSet.insert(extension.name);
                allRequestedExtensions.push_back(extension);
            }
        }
        // GLFW扩展
        for (int i = 0; i < glfwRequestedExtensionCount; ++i)
        {
            const char* extensionName = glfwRequestedExtensions[i];
            if (allRequestedExtensionSet.find(extensionName) == allRequestedExtensionSet.end())
            {
                allRequestedExtensionSet.insert(extensionName);
                allRequestedExtensions.push_back({ extensionName, true });
            }
        }


        // 加载必要扩展
        uint32_t enableExtensionCount;
        const char* enableExtensions[32];
        if (!checkDeviceFeatures("Instance Extension", true, availableExtensionCount, availableExtensions, allRequestedExtensions.size(),
            allRequestedExtensions.data(), &enableExtensionCount, enableExtensions))
        {
            return;
        }
        // 创建实例
        VkApplicationInfo applicationInfo =
        {
            .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
            .pNext = nullptr,
            .pApplicationName = "PiccoloEngine",
            .applicationVersion = VK_MAKE_VERSION(1, 0, 0),
            .pEngineName = "None",
            .engineVersion = VK_MAKE_VERSION(1, 0, 0),
            .apiVersion = VK_API_VERSION_1_3
        };

        VkDebugReportCallbackCreateInfoEXT debugReportCallbackCreateInfoExt =
        {
            .sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT,
            .pNext = nullptr,
            .flags = VK_DEBUG_REPORT_WARNING_BIT_EXT | VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT | VK_DEBUG_REPORT_ERROR_BIT_EXT,
            .pfnCallback = VkDebugReportCallback
        };

        VkInstanceCreateInfo createInfo =
        {
            .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
            .pNext = bShouldValidate ? &debugReportCallbackCreateInfoExt : nullptr,
            .flags = 0,
            .pApplicationInfo = &applicationInfo,
            .enabledLayerCount = enableLayerCount,
            .ppEnabledLayerNames = enableLayerCount > 0 ? enableLayers : nullptr,
            .enabledExtensionCount = enableExtensionCount,
            .ppEnabledExtensionNames = enableExtensionCount > 0 ? enableExtensions : nullptr,
        };
        CALL_VK(vkCreateInstance(&createInfo, nullptr, &mInstance));
        LOG_T("{0} : instance : {1}", __FUNCTION__, (void*)(mInstance));
    }

    void PiccoloVKGraphicsContext::CreateSurface(PiccoloWindow* window)
    {
        if (!window)
        {
            LOG_E("window is not exists.");
            return;
        }
        auto* glfwWindow = dynamic_cast<PiccoloGLFWWindow*>(window);
        if (!glfwWindow)
        {
            LOG_E("this window is not a glfw window.");
            return;
        }
        GLFWwindow* implWindowPointer = static_cast<GLFWwindow*>(glfwWindow->GetImplWindowPointer());
        CALL_VK(glfwCreateWindowSurface(mInstance, implWindowPointer, nullptr, &mSurface));
        LOG_T("{0} : surface : {1}", __FUNCTION__, (void*)(mSurface));
    }

    /**
     * 选取最合适的物理设备
     */
    void PiccoloVKGraphicsContext::findPhysicalDevice()
    {
        uint32_t physicalDeviceCount = 0;
        CALL_VK(vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount, nullptr));
        VkPhysicalDevice physicalDevices[physicalDeviceCount];
        CALL_VK(vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount, physicalDevices));

        // 打分选取
        uint32_t maxScore = 0;
        int32_t maxScorePhysicalDeviceIndex = -1;
        LOG_D("-----------------------");
        LOG_D("Physical Device: ");
        for (int i = 0; i < physicalDeviceCount; ++i)
        {
            VkPhysicalDeviceProperties props;
            vkGetPhysicalDeviceProperties(physicalDevices[i], &props);
            PrintPhysicalDeviceInfo(props);

            // 打分
            uint32_t score = GetPhysicalDeviceScore(props);
            // 如果还支持某些制定格式，给予更高的分数
            uint32_t formatCount;
            CALL_VK(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevices[i], mSurface, &formatCount, nullptr));
            VkSurfaceFormatKHR formats[formatCount];
            CALL_VK(vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevices[i], mSurface, &formatCount, formats));
            for (int j = 0; j < formatCount; ++j)
            {
                if (formats[j].format == VK_FORMAT_B8G8R8A8_UNORM && formats[j].colorSpace == VK_COLORSPACE_SRGB_NONLINEAR_KHR)
                {
                    score += 10;
                    break;
                }
            }

            // 查询队列簇
            uint32_t queueFamilyCount;
            vkGetPhysicalDeviceQueueFamilyProperties(physicalDevices[i], &queueFamilyCount, nullptr);
            VkQueueFamilyProperties queueFamilies[queueFamilyCount];
            vkGetPhysicalDeviceQueueFamilyProperties(physicalDevices[i], &queueFamilyCount, queueFamilies);

            LOG_D("score          --->   : {0}", score);
            LOG_D("queue family   --->   : {0}", queueFamilyCount);
            if (score < maxScore) continue;

            for (int j = 0; j < queueFamilyCount; ++j)
            {
                if (queueFamilies[j].queueCount == 0) continue;
                // 检查这个队列簇是否可以做图形渲染管线 graphic family
                if (queueFamilies[j].queueFlags & VK_QUEUE_GRAPHICS_BIT)
                {
                    mGraphicQueueFamily.queueFamilyIndex = j;
                    mGraphicQueueFamily.queueCount = queueFamilies[j].queueCount;
                }

                if (mGraphicQueueFamily.queueFamilyIndex >= 0 && mPresentQueueFamily.queueFamilyIndex >= 0
                    && mGraphicQueueFamily.queueFamilyIndex != mPresentQueueFamily.queueFamilyIndex)
                {
                    break;
                }

                // 检查这个队列簇是否可以做显示 present family
                VkBool32 bSupportSurface;
                vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevices[i], j, mSurface, &bSupportSurface);
                if (bSupportSurface)
                {
                    mPresentQueueFamily.queueFamilyIndex = j;
                    mPresentQueueFamily.queueCount = queueFamilies[j].queueCount;
                }
            }

            if (mGraphicQueueFamily.queueFamilyIndex >= 0 && mPresentQueueFamily.queueFamilyIndex >= 0)
            {
                maxScorePhysicalDeviceIndex = i;
                maxScore = score;
            }
        }
        LOG_D("-----------------------");

        if (maxScorePhysicalDeviceIndex < 0)
        {
            LOG_W("Maybe can not find a suitable physical device, will 0.");
            maxScorePhysicalDeviceIndex = 0;
        }
        mPhysicalDevice = physicalDevices[maxScorePhysicalDeviceIndex];
        vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &mPhysicalDeviceMemoryProperties);
        LOG_T("{0} : physical device: {1}, score: {2}, graphic queue: {3} : {4}, present queue: {5} : {6}", __FUNCTION__, maxScorePhysicalDeviceIndex, maxScore,
            mGraphicQueueFamily.queueFamilyIndex, mGraphicQueueFamily.queueCount, mPresentQueueFamily.queueFamilyIndex, mPresentQueueFamily.queueCount);
    }

    void PiccoloVKGraphicsContext::PrintPhysicalDeviceInfo(VkPhysicalDeviceProperties &props)
    {
        const char* deviceType = props.deviceType == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU ? "intergrated gpu" :
                                 props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU ? "discrete gpu" :
                                 props.deviceType == VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU ? "virtual gpu" :
                                 props.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU ? "cpu" : "unknown";

        uint32_t driverVersionMajor = VK_VERSION_MAJOR(props.driverVersion);
        uint32_t driverVersionMinor = VK_VERSION_MINOR(props.driverVersion);
        uint32_t driverVersionPatch = VK_VERSION_PATCH(props.driverVersion);

        uint32_t apiVersionMajor = VK_VERSION_MAJOR(props.apiVersion);
        uint32_t apiVersionMinor = VK_VERSION_MINOR(props.apiVersion);
        uint32_t apiVersionPatch = VK_VERSION_PATCH(props.apiVersion);

        LOG_D("-------------------");
        LOG_D("deviceName      : {0}", props.deviceName);
        LOG_D("deviceType      : {0}", deviceType);
        LOG_D("vendorID        : {0}", props.vendorID);
        LOG_D("deviceID        : {0}", props.deviceID);
        LOG_D("deviceVersion   : {0}.{1}.{2}", driverVersionMajor, driverVersionMinor, driverVersionPatch);
        LOG_D("apiVersion      : {0}.{1}.{2}", apiVersionMajor, apiVersionMinor, apiVersionPatch);
    }

    uint32_t PiccoloVKGraphicsContext::GetPhysicalDeviceScore(VkPhysicalDeviceProperties &props)
    {
        VkPhysicalDeviceType deviceType = props.deviceType;
        uint32_t score = 0;
        switch (deviceType)
        {
            case VK_PHYSICAL_DEVICE_TYPE_OTHER:
                break;
            case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
                score += 40;
                break;
            case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
                score += 30;
                break;
            case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
                score += 20;
                break;
            case VK_PHYSICAL_DEVICE_TYPE_CPU:
                score += 10;
                break;
            case VK_PHYSICAL_DEVICE_TYPE_MAX_ENUM:
                break;
        }
        return score;
    }
}
